package com.icesoft.core.dao.listener;

import com.icesoft.core.dao.base.Model;
import com.icesoft.core.dao.criteria.QueryBuilder;
import com.icesoft.core.dao.suppose.HqlQuerySuppose;
import com.icesoft.core.dao.suppose.IDaoInterceptor;
import com.icesoft.core.dao.suppose.validation.UniqueCheck;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

@Component
@AllArgsConstructor
class UniqueCheckValidator implements IDaoInterceptor {

    private HqlQuerySuppose hqlQuerySuppose;

    @Override
    public void onCreate(Model model) {
        valid(model);
    }

    @Override
    public void onUpdate(Model model) {
        valid(model);
    }

    private static Map<Class, List<FieldDTO>> classFieldListMap = new WeakHashMap<>();

    private void valid(Model model) {
        List<FieldDTO> fieldDTOList = classFieldListMap.get(model.getClass());
        if (fieldDTOList == null) {
            fieldDTOList = initFieldDTO(model.getClass());
        }
        if (fieldDTOList.isEmpty()) {
            return;
        }
        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(model);
        for (FieldDTO fieldDTO : fieldDTOList) {
            Object val = beanWrapper.getPropertyValue(fieldDTO.field.getName());
            if (val == null) {
                continue;
            }
            QueryBuilder qb = QueryBuilder.get();
            if (model.getId() != null) {
                qb.andNe("id", model.getId());
            }
            qb.andEq(fieldDTO.field.getName(), val);
            for (String name : fieldDTO.uniqueCheck.cascade()) {
                qb.andEq(name, beanWrapper.getPropertyValue(name));
            }
            if (hqlQuerySuppose.isExist(qb.from(model.getClass()))) {
                throw new IllegalArgumentException(fieldDTO.uniqueCheck.message());
            }
        }
    }

    private static synchronized List<FieldDTO> initFieldDTO(Class clazz) {
        List<FieldDTO> fieldDTOList = classFieldListMap.get(clazz);
        if (fieldDTOList == null) {
            fieldDTOList = new ArrayList<>();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                UniqueCheck unique = field.getAnnotation(UniqueCheck.class);
                if (unique != null) {
                    fieldDTOList.add(new FieldDTO(field, unique));
                }
            }
            classFieldListMap.put(clazz, fieldDTOList);
        }
        return fieldDTOList;
    }

    @AllArgsConstructor
    private static class FieldDTO {
        Field field;
        UniqueCheck uniqueCheck;
    }
}  
